package com.xxx.springboot.controller;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.log.StaticLog;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import com.github.yitter.idgen.YitIdHelper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.eventbus.AsyncEventBus;
import com.mongodb.client.result.DeleteResult;
import com.xxl.conf.core.XxlConfClient;
import com.xxl.conf.core.annotation.XxlConf;
import com.xxx.springboot.annotation.AccessAuth;
import com.xxx.springboot.annotation.AutoIdempotent;
import com.xxx.springboot.annotation.NotRepeatSubmit;
import com.xxx.springboot.constant.RedisConstants;
import com.xxx.springboot.listener.SystemListener;
import com.xxx.springboot.pojo.Result;
import com.xxx.springboot.pojo.UserInfo;
import com.xxx.springboot.pojo.bean.PubSubMsg;
import com.xxx.springboot.pojo.dto.SignData;
import com.xxx.springboot.pojo.entity.RelationShip;
import com.xxx.springboot.pojo.entity.Student;
import com.xxx.springboot.pojo.entity.Teacher;
import com.xxx.springboot.pojo.entity.User;
import com.xxx.springboot.pojo.enums.RedisDelayQueueEnum;
import com.xxx.springboot.pojo.vo.UserExportVO;
import com.xxx.springboot.repository.RelationshipRepository;
import com.xxx.springboot.repository.StudentRepository;
import com.xxx.springboot.repository.TeacherRepository;
import com.xxx.springboot.service.*;
import com.xxx.springboot.service.impl.BloomFilterService;
import com.xxx.springboot.service.impl.OrderService;
import com.xxx.springboot.service.impl.RedisPubsubService;
import com.xxx.springboot.util.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.poi.ss.usermodel.Workbook;
import org.joda.time.DateTime;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Metrics;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.io.ByteArrayOutputStream;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * demo Controller
 */
@Controller
@RequestMapping(value = "/", produces = "application/json;charset=utf-8")
public class DemoController extends BaseController {
	
	private static final Logger logger = LoggerFactory.getLogger(DemoController.class);
	
	@XxlConf("default.key02")
	public String paramByAnno;

	@ApolloConfig
	private Config config;

	@Autowired
	private DemoService demoService;

	@Autowired
	private UserService userService;
	
	@Autowired
	private AsyncEventBus asyncEventBus;
	
	@Autowired
	private MongoUtil mongoUtil;
	
	@Autowired
    private TokenService tokenService;
	
	@Autowired
	private OrderService orderService;

	@Autowired
	private RedisDelayQueueService redisDelayQueueService;

	@Autowired
	private BloomFilterService bloomFilterService;

	@Autowired
	private RedisPubsubService redisPubsubService;

	@Autowired
	private MailService mailService;

	@Autowired
	private StudentRepository studentRepository;
	@Autowired
	private TeacherRepository teacherRepository;
	@Autowired
	private RelationshipRepository relationshipRepository;

	@Autowired
	private DisruptorMqService disruptorMqService;

	@Autowired
	private TestRetryService testRetryService;
	
//	static {
//        /**
//         * 配置变更监听示例：可开发Listener逻辑，监听配置变更事件；可据此实现动态刷新JDBC连接池等高级功能；
//         */
//        XxlConfClient.addListener("default.key04", new XxlConfListener(){
//            @Override
//            public void onChange(String key, String value) throws Exception {
//                logger.info("配置变更事件通知：{}={}", key, value);
//            }
//        });
//    }

//	// apollo配置变化监听器
//	static {
//		Config config = ConfigService.getAppConfig(); // 获取默认namespace（application）的config
//		config.addChangeListener(new ConfigChangeListener() {
//			@Override
//			public void onChange(ConfigChangeEvent configChangeEvent) {
//				if(configChangeEvent.isChanged("changeKey")) {
//					String oldValue = configChangeEvent.getChange("changeKey").getOldValue();
//					String newValue = configChangeEvent.getChange("changeKey").getNewValue();
//					logger.info("apollo配置变更事件通知：key:{}, oldValue:{}, newValue:{}", "changeKey", oldValue, newValue);
//				}
//			}
//		});
//
//	}

	
	/**
	 * Guava cache 
	 */
	private LoadingCache<String, User> userCache = CacheBuilder.newBuilder()
						// 初始化容量（默认16）
						.initialCapacity(1)
						// 缓存容量（最多保存100个对象）
						.maximumSize(100)
						
						// 多长时间内没有读/写操作就会过期（60秒内没有被读/写就会移除key，不要被字面含义误解为：访问后多长时间过期）
						.expireAfterAccess(60, TimeUnit.SECONDS)
						// 多长时间内没有写操作就会过期（10秒内没有被创建/更新就会移除key）
						// 注意：当同时存在expireAfterAccess和expireAfterWrite时，expireAfterWrite会覆盖expireAfterAccess设置
						.expireAfterWrite(10, TimeUnit.SECONDS)
						// 多长时间内没有写操作，再次访问时就会刷新缓存，在新值到来之前，始终返回旧值（60秒内没有发生创建/更新操作，如果再次被访问会刷新缓存，在新值没有到来之前始终返回旧值）
						// 注意：开启refreshAfterWrite后，必须要我们去实现一个CacheLoader，否则会启动异常。
						.refreshAfterWrite(60, TimeUnit.SECONDS)
						
						// 打开统计功能，例如命中率等
						.recordStats()
						
						// 使用CacheLoader创建一个LoadingCache对象
						.build(new CacheLoader<String, User>() {
			
							@Override
							public User load(String key) throws Exception {
								System.out.println("value does not exist in cache. load from DB. key:"+ key);
								return userService.getById(key);
							}
							
						});

    
    /**
     * 测试guava cache
     * @param userId
     * @return
     * @throws ExecutionException 
     */
    @RequestMapping("/testGuavaCache")
    @ResponseBody
    public User testGuavaCache(String userId) throws ExecutionException {
    	System.out.println("--------------------enter method testGuavaCache----------------------");
        User user = userCache.get(userId);
        logger.info("user:{}", user==null ? "null" : user.toString());
        System.out.println("--------------------exit method testGuavaCache----------------------");
        return user;
    }
    
	/**
     * 系统启动时间戳
     */
    @RequestMapping("/versionNo")
    @ResponseBody
    public String versionNo(){
    	logger.info("enter method versionNo().");
        return SystemListener.versionNo;
    }

	/**
	 * 测试雪花算法
	 * @return
	 */
	@RequestMapping("/testSnowflakeId")
	@ResponseBody
	public Long testSnowflakeId(String a) {
		return SnowflakeIdWorker.generateId();
	}

	/**
	 * 测试雪花算法短ID
	 * @return
	 */
	@RequestMapping("/testSnowflakeShortId")
	@ResponseBody
	public Long testSnowflakeShortId(String a) {
		return YitIdHelper.nextId();
	}

	/**
	 * 集成freemarker
	 * @return
	 */
	@RequestMapping("index")
	public String index(){
		return "index";
	}

	/**
	 * hutool小工具类
	 */
	@RequestMapping("/hutool")
	@ResponseBody
    public String hutool(){
    	StaticLog.info("enter method hutool(). currentTime:{}", System.currentTimeMillis());
        return SecureUtil.md5("test1");
    }

    /**
     * 测试防重复提交
     * @return
     */
	@NotRepeatSubmit(5000)
	@RequestMapping("/api/NotRepeatSubmit")
	@ResponseBody
	public String NotRepeatSubmit() {
		return "ok";
	}
    
    /**
     * 测试拦截器和过滤器
     * @param a
     * @return
     */
    @RequestMapping("/testFilter")
    @ResponseBody
    public String testFilter(String a) {
        return "我是controller1";
    }
    
    /**
     * 测试异步任务
     */
    @RequestMapping("/testAsync")
    @ResponseBody
    public String testAsync(){
    	logger.info("enter method testAsync().");
    	try {
			orderService.cancelOrder();
			return "success";
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
    	return "fail";
    }
    
    /**
     * 分页查询列表
     * @return
     */
    @Cached(cacheType = CacheType.BOTH, expire = 60)
    @AccessAuth(anyPermissionCodes = "pageList")
    @RequestMapping("/pageList")
    @ResponseBody
    public String pageList(@RequestParam(defaultValue = "1") @Min(1) Integer pageNo,
                           @RequestParam(defaultValue = "10") @Min(1) Integer pageSize,
                           String userName) {
    	logger.info("enter method pageList(). userName:{}", userName);
        try {
        	LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        	if(!StringUtils.isEmpty(userName)) {
        		queryWrapper.likeRight(User::getUserName, userName);
        	}
        	IPage<User> page = new Page<>(pageNo, pageSize);
        	IPage<User> pageList = userService.page(page, queryWrapper);
            return Result.ok(pageList);
        } catch (Exception e) {
        	logger.error("pageList发生异常：{}", e);
            return Result.fail("发生异常");
        }
    }
    
    /**
     * 根据name查询
     */
    @AccessAuth(anyPermissionCodes = "getByName")
    @RequestMapping("/getByName")
    @ResponseBody
    public String getByName(String username) {
    	logger.info("enter method getByName(). username:{}", username);
        try {
        	User user = userService.getByName(username);
            return Result.ok(user);
        } catch (Exception e) {
        	logger.error("getByName发生异常：{}", e);
            return Result.fail("发生异常");
        }
    }

	/**
	 * 4.测试JTA分布式事务
	 */
	@RequestMapping("/testJTA")
	@ResponseBody
	public int testJTA(){
		logger.info("enter method testJTA().");
		int successNum = demoService.testJTA();
		return successNum;
	}
    
	/**
     * 3.测试redis自动过期事件监听
     */
    @RequestMapping("/testRedis")
    @ResponseBody
    public String testRedis(){
    	logger.info("enter method testRedis().");
    	User entity = new User();
    	entity.setMobile("18600017641");
    	entity.setEmail("test@abc.com");
    	entity.setNote("");
    	entity.setPositionId(2);
    	entity.setRealName("小强哥");
    	entity.setSex(1);
    	entity.setUserName("guweiqiang");
    	String json = JsonUtil.encodeJson(entity);
    	RedisUtil.set("user", json, 10); // 过期时间：10s
    	return Result.ok(entity);
    }

	/**
     * 测试set redis
     */
    @RequestMapping("/setRedis")
    @ResponseBody
    public String setRedis(HttpServletRequest request){
    	logger.info("enter method setRedis().");
    	
    	String redisKey = request.getParameter("key");
    	if(StringUtils.isEmpty(redisKey)) {
    		return Result.fail("key is null");
    	}
    	String value = request.getParameter("value");
    	if(StringUtils.isEmpty(value)) {
    		return Result.fail("value is null");
    	}
		RedisUtil.set(redisKey, value, 600);
    	return Result.ok();
    }

	/**
	 * 测试get redis
	 */
	@RequestMapping("/getRedis")
	@ResponseBody
	public String getRedis(HttpServletRequest request){
		logger.info("enter method getRedis().");

		String redisKey = request.getParameter("key");
		if(StringUtils.isEmpty(redisKey)) {
			return Result.fail("key is null");
		}
		String json = RedisUtil.get(redisKey);
		if(StringUtils.isEmpty(json)) {
			return Result.fail("json is null");
		}
		return Result.ok(json);
	}

	/**
	 * 测试 geo
	 */
	@RequestMapping("/testGeo")
	@ResponseBody
	public String testGeo(HttpServletRequest request){
		logger.info("enter method testGeo().");

		String redisKey = RedisConstants.REDIS_KEY_GEO_POINTS;

		// 测试添加经纬度坐标点member
//		Long r1 = RedisUtil.geoAdd(redisKey, "dongcheng", 116.410593,39.926445);
//		Long r2 = RedisUtil.geoAdd(redisKey, "chaoyang", 116.508328,39.986619);
//		Long r3 = RedisUtil.geoAdd(redisKey, "haidian", 116.215696,39.96229);
//		Long r4 = RedisUtil.geoAdd(redisKey, "tongzhou", 116.66873,39.913607);
//		Long r5 = RedisUtil.geoAdd(redisKey, "daxing", 116.446813,39.805937);
//		List<Long> rList = new ArrayList<>();
//		rList.add(r1);
//		rList.add(r2);
//		rList.add(r3);
//		rList.add(r4);
//		rList.add(r5);
//		return Result.ok(rList);

		// 获取两个member之间距离
//		RedisUtil.geoAdd(redisKey, "beijing", 116.403829,39.915351);
//		RedisUtil.geoAdd(redisKey, "tianjing", 117.218439,39.142162);
//		Double dist = RedisUtil.geoDist(redisKey, "beijing", "tianjing");
//		return Result.ok(dist);

		// 判断指定成员指定半径范围内的其他成员
		RedisUtil.geoAdd(redisKey, "shijiazhuang", 114.490484,38.016934);
		RedisUtil.geoAdd(redisKey, "shanghai", 121.437421,31.159324);
		List<String> list = RedisUtil.geoRadius(redisKey, 116.403829,39.915351, 200, Metrics.KILOMETERS);
		return Result.ok(list);
	}
    
	/**
     * 测试mongoDB
     */
    @RequestMapping("/setMongo")
    @ResponseBody
    public String setMongo(){
    	logger.info("enter method setMongo().");
    	UserInfo userInfo = new UserInfo();
    	userInfo.setUserId(1);
    	userInfo.setUserName("guweiqiang");
    	userInfo.setRealName("顾伟强");
    	userInfo.setSex(1);
    	userInfo.setMobile("18600017641");
    	userInfo.setEmail("guweiqiang@58.com");
    	userInfo.setPositionId(2);
    	mongoUtil.save(userInfo);
    	return Result.ok(userInfo);
    }
    
	/**
     * 测试mongoDB
     */
    @RequestMapping("/updateMongo")
    @ResponseBody
    public String updateMongo(){
    	logger.info("enter method updateMongo().");
    	UserInfo userInfo = new UserInfo();
    	userInfo.setUserId(1);
    	userInfo.setUserName("guweiqiang");
    	userInfo.setRealName("顾伟强1");
    	userInfo.setSex(1);
    	userInfo.setMobile("18600017641");
    	userInfo.setEmail("guweiqiang@58.com");
    	userInfo.setPositionId(2);
    	mongoUtil.update(userInfo);
    	return Result.ok(userInfo);
    }

	/**
     * 测试mongoDB
     */
    @RequestMapping("/getMongo")
    @ResponseBody
    public String getMongo(HttpServletRequest request){
    	logger.info("enter method getMongo().");
    	
    	String username = request.getParameter("username");
    	if(StringUtils.isEmpty(username)) {
    		return Result.fail("username is null");
    	}
    	List<UserInfo> list = mongoUtil.find(username);
    	return Result.ok(list);
    }
    
	/**
     * 测试mongoDB
     */
    @RequestMapping("/deleteMongo")
    @ResponseBody
    public String deleteMongo(HttpServletRequest request){
    	logger.info("enter method deleteMongo().");
    	String id = request.getParameter("id");
    	if(StringUtils.isEmpty(id)) {
    		return Result.fail("id is null");
    	}
    	DeleteResult deleteResult = mongoUtil.delete(Integer.parseInt(id));
    	return Result.ok(deleteResult);
    }
    
	/**
     * 测试EventBus
     */
    @RequestMapping("/testEventBus")
    @ResponseBody
    public String testEventBus(@RequestParam @NotNull String username){
    	logger.info("enter method testEventBus().");
    	asyncEventBus.post(username); // EventBus发布者
    	return Result.ok();
    }
    
    /**
     * 【redisson分布式锁】4.测试 Redisson分布式锁
     */
    @RequestMapping("/testRedissonLock")
    @ResponseBody
    public String testRedissonLock(@RequestParam @NotNull String key){
    	logger.info("enter method testRedissonLock().");
    	String lockKey = "redisson:lockKey:"+key;
		RLock lock = RedissLockUtil.lock(lockKey);
    	if(RedissLockUtil.tryLock(lockKey, 10, 30)) { // 超时等待10s，上锁后自动释放锁时间30s
    		return Result.ok(lock.isExists());
    	} else {
    		return Result.fail();
    	}
    }

	/**
	 * 【zookeeper分布式锁】
	 */
	@RequestMapping("/testZookeeperLock")
	@ResponseBody
	public String testZookeeperLock() throws Exception {
		logger.info("enter method testZookeeperLock().");
		// 重试策略：每隔5s重试一次，最多重试3次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(5000, 3);
		// 创建zookeeper客户端
		CuratorFramework client = CuratorFrameworkFactory.builder()
				.connectString("127.0.0.1:2181")
				.retryPolicy(retryPolicy)
				.build();
		// 启动客户端，连接到zookeeper
		client.start();
		// 声明锁对象，本质是zk临时有序节点
		final InterProcessMutex mutex = new InterProcessMutex(client, "/locks/test");
		// 分布式锁
		try {
			// 获取锁
			mutex.acquire();
			// 业务逻辑处理
			Thread.sleep(1000);
			return Result.ok();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放锁
			mutex.release();
		}
		return Result.fail();
	}

	/**
	 * 【redisson布隆过滤器】2.测试 布隆过滤器
	 */
	@RequestMapping("/testBloomFilter")
	@ResponseBody
	public String testBloomFilter(){
		logger.info("enter method testBloomFilter().");
		// 往布隆过滤器里添加10万条记录
		for (int i = 0; i < 100000; i++) {
			bloomFilterService.put(Long.valueOf(i));
		}

		System.out.println("插入10万条记录完成");

		// 用另外10万条记录测试误判率
		int wrongTimes = 0; // 误判次数
		for (int i = 100000; i < 200000; i++) {
			if(bloomFilterService.checkContain(Long.valueOf(i))){
				System.out.println(i+"被误判了");
				wrongTimes++;
			}
		}
		String result = "总共误判了"+wrongTimes+"次，误判率为："+DoubleUtil.calcPercent(wrongTimes, 100000);
		System.out.println(result);
		return Result.ok(result);
	}

    /**
     * 获取幂等用token
     * @return
     */
    @RequestMapping("/getToken")
    @ResponseBody
    public String getToken(){
        String token = tokenService.createToken();
        if (StringUtils.isNotEmpty(token)) {
            return Result.ok(token);
        }
        return Result.fail();
    }

    /**
     * 校验请求幂等性
     * @return
     */
    @AutoIdempotent
    @RequestMapping("/testIdempotence")
    @ResponseBody
    public String testIdempotence() {
    	User entity = new User();
    	entity.setMobile("18600017641");
    	entity.setEmail("test@abc.com");
    	entity.setNote("");
    	entity.setPositionId(2);
    	entity.setRealName("顾伟强");
    	entity.setSex(1);
    	entity.setUserName("guweiqiang");
        boolean result = userService.save(entity);
        if(result) {
        	return Result.ok(result);
        }
        return Result.fail();
    }
    
    /**
     * 测试sofaboot
     * @param name
     * @return
     */
    @RequestMapping("/sofaboot")
    @ResponseBody
    public Map<String, Object> sofaboot(@RequestParam(value = "name", defaultValue = "SOFATracer SpringMVC DEMO") String name) {
        Map<String, Object> resultMap = new HashMap<String, Object>();
        resultMap.put("success", true);
        resultMap.put("id", new AtomicLong().incrementAndGet());
        resultMap.put("content", String.format("Hello, %s!", name));
        return resultMap;
    }

	/**
	 * 测试xxl-conf
	 */
	@RequestMapping("/testXxlConf")
	@ResponseBody
	public String testXxlConf(){
		logger.info("enter method testXxlConf().");
		List<String> list = new LinkedList<>();
		String paramByApi = XxlConfClient.get("default.key04", null);
		list.add("1、API方式: default.key04=" + paramByApi);
		list.add("2、@XxlConf 注解方式: default.key02=" + paramByAnno);
		return Result.ok(list);
	}

	/**
	 * 测试apollo config
	 */
	@RequestMapping("/testApolloConf")
	@ResponseBody
	public String testApolloConf(){
		logger.info("enter method testApolloConf().");
//		Config config = ConfigService.getAppConfig();
		String value = config.getProperty("test.apollo.key", "defaultValue");
		return Result.ok(value);
	}
    
    /**
     * 【Redis bitmap签到】1.模拟用户签到
	 * n：用户在今天以前n天进行签到
     */
    @RequestMapping("/signup")
    @ResponseBody
    public String signup(String userId, int n) {
    	User user = userService.getById(userId);
    	if(user==null) {
    		return Result.fail("user不存在");
    	}

    	// 本月第1天开始统计
    	DateTime startDate = JodaUtil.getFirstDayOfMonth();
    	// 签到日期
		DateTime signDate = JodaUtil.someDaysAgo(new DateTime(), n);
    	// 间隔天数
    	int days = JodaUtil.getDaysInterval(startDate, signDate);
    	String currentMonth = JodaUtil.currentMonth();
    	
    	// 每月一个key：signup:userId:yyyyMM（如果按年统计，则key为yyyyMMdd）
    	String key = "signup:"+user.getId()+":"+currentMonth;
    	long offset = days%30; // 取模(当月30天)

		// bitmap签到
    	boolean result = RedisUtil.setbit(key, offset, true);
    	if(result) { // 已签到
    		return Result.fail("已签到");
    	}
    	return Result.ok("签到成功");
    }
    
    /**
     * 【Redis bitmap签到】2.统计某个用户指定月份的全部签到次数
     */
    @RequestMapping("/getUserTotalSignCount")
    @ResponseBody
    public String getUserTotalSignCount(String userId) {
    	User user = userService.getById(userId);
    	if(user==null) {
    		return Result.fail("user不存在");
    	}
    	
    	String currentMonth = JodaUtil.currentMonth();
		// 每月一个key：signup:userId:yyyyMM
    	String key = "signup:"+user.getId()+":"+currentMonth;

		// bitmap获取用户当月的全部签到次数
    	long cnt = RedisUtil.bitcount(key);
		return Result.ok(cnt);
    }
    
    /**
     * 【Redis bitmap签到】3.统计某个用户指定月份的连续N天内的签到次数
     */
    @RequestMapping("/getUserLastNDaySigncount")
    @ResponseBody
    public String getUserLastNDaySigncount(String userId, int N) {
    	User user = userService.getById(userId);
    	if(user==null) {
    		return Result.fail("user不存在");
    	}
    	
    	String currentMonth = JodaUtil.currentMonth();
		// 每月一个key：signup:userId:yyyyMM
    	String key = "signup:"+user.getId()+":"+currentMonth;

    	long start = -1; // -1表示从末尾开始，向前计算
    	long end = -(N/8);  // 向前N天（字节换成bit）

    	// bitmap获取用户当月内最近连续N天内的签到次数
    	long cnt = RedisUtil.bitcount(key, start, end); // 有坑：start、end指的是byte，不是bit
		return Result.ok(cnt);
    }

	/**
	 * 【Redis bitmap签到】4.获取用户指定月份1日开始N天内的签到状态列表(具体哪天签到过)
	 */
	@RequestMapping("/getUserSignDateList")
	@ResponseBody
	public String getUserSignDateList(String userId, int N) {
		User user = userService.getById(userId);
		if(user==null) {
			return Result.fail("user不存在");
		}

		String currentMonth = JodaUtil.currentMonth();
		// 每月一个key：signup:userId:yyyyMM
		String key = "signup:"+user.getId()+":"+currentMonth;

		DateTime startDate = JodaUtil.getFirstDayOfMonth();

		// 返回结果列表
		List<SignData> list = new ArrayList<>();
		SignData signData = null;
		String date = ""; // 日期
		int days = 0; // 间隔天数
		for (int i = 0; i < N; i++) { // 当月有N天
			signData = new SignData();
			date = JodaUtil.dateTimeToString(JodaUtil.someDaysAfter(startDate, i), JodaUtil.DATE_PATTERN_DEFAULT);
			signData.setDate(date);

			days = i;
			long offset = days%30; // 取模方式和签到时一致

			// bitmap获取签到状态
			boolean result = RedisUtil.getbit(key, offset);
			if(result){ // 已签到
				signData.setIsSign(1);
			} else { // 未签到
				signData.setIsSign(0);
			}
			list.add(signData);
		}

		return Result.ok(list);
	}

	/**
	 * 【redis延时队列】测试redis延时队列添加任务
	 */
	@RequestMapping("/testAddRedisDelayQueue")
	@ResponseBody
	public String testAddRedisDelayQueue(Long orderId) throws InterruptedException {
		logger.info("enter method testAddRedisDelayQueue().");

//		redisDelayQueueService.addTaskIntoDelayQueue(orderId, 10, TimeUnit.SECONDS, RedisDelayQueueEnum.ORDER_PAYMENT_TIMEOUT.getQueueName());

		// 订单支付超时延时队列
		for(int i=1; i<=10; i++) {
			orderId = Long.valueOf(i);
			redisDelayQueueService.addTaskIntoDelayQueue(orderId, 10, TimeUnit.SECONDS, RedisDelayQueueEnum.ORDER_PAYMENT_TIMEOUT.getQueueName());
		}

		Thread.sleep(10000);

		// 订单评价超时延时队列
		for(int i=11; i<=20; i++) {
			orderId = Long.valueOf(i);
			redisDelayQueueService.addTaskIntoDelayQueue(orderId, 10+i, TimeUnit.SECONDS, RedisDelayQueueEnum.ORDER_NOT_EVALUATED_TIMEOUT.getQueueName());
		}

		return Result.ok(orderId);
	}

	/**
	 * 【Redis 发布/订阅】测试 Redisson 发布/订阅
	 */
	@RequestMapping("/testRedissonPubSub")
	@ResponseBody
	public String testRedissonPubSub(){
		logger.info("enter method testRedissonPubSub().");

		int totalTimes = 1000;
		int successTimes = 0;
		for (int i = 0; i < totalTimes; i++) {
			PubSubMsg msg = new PubSubMsg();
			msg.setId(i);
			msg.setName("user"+i);
			long receivedClientNum = redisPubsubService.publish(msg);
			if(receivedClientNum>0) { // 发布成功
				successTimes++;
			} else {
				logger.info(i+"发布消息失败：receivedClientNum="+receivedClientNum);
			}
		}

		return Result.ok("总计发布了"+totalTimes+"次，成功了"+successTimes+"次");
	}

	/**
	 * 4. 测试easy poi上传导入excel
	 */
	@RequestMapping("/importExcel")
	@ResponseBody
	public String importExcel(@RequestParam("file") MultipartFile file){
		try {
			return demoService.importExcelByEasyPoi(file);
		} catch (Exception e) {
			logger.error("importExcel_exception:{}", e);
		}
		return Result.fail("exception");
	}

	/**
	 * 5. 测试easy poi导出excel
	 */
	@RequestMapping("/exportExcel")
	@ResponseBody
	public String exportExcel(HttpServletResponse response){
		try {
			// 获取excel导出列表数据
			List<UserExportVO> list = demoService.exportExcelByEasyPoi();
			// 设置导出参数
			ExportParams exportParams = new ExportParams();
			exportParams.setTitle("测试easy poi导出");
			exportParams.setSheetName("sheet1");
			// 创建excel workbook
			Workbook workbook = ExcelExportUtil.exportExcel(exportParams, UserExportVO.class, list);
//			// 方式一：将excel文件输出到磁盘
//			FileOutputStream fos = new FileOutputStream("/opt/"+"easypoi_test.xlsx");
//			workbook.write(fos);
//			fos.close();
			// 方式二：下载excel文件
			ByteArrayOutputStream os = new ByteArrayOutputStream();
			workbook.write(os);
			byte[] bytes = os.toByteArray();
			String fileName = "easypoi_download.xlsx";

			response.reset();
			response.setContentType("application/msexcel;charset=utf-8");
			response.setHeader("Content-disposition", "attachment;filename= "+ fileName);
			response.getOutputStream().write(bytes);
			response.getOutputStream().flush();
			response.getOutputStream().close();

			return Result.ok();
		} catch (Exception e) {
			logger.error("exportExcel_exception:{}", e);
		}
		return Result.fail("exception");
	}

	/**
	 * 测试sa-token登录
	 * http://localhost:8010/saDoLogin?username=zhang&password=123456
	 * @param username
	 * @param password
	 * @return
	 */
	@RequestMapping("saDoLogin")
	@ResponseBody
	public String saDoLogin(String username, String password) {
		// 此处仅作模拟示例，真实项目需要从数据库中查询数据进行比对
		if("zhang".equals(username) && "123456".equals(password)) {
			StpUtil.login(10001);
			return Result.ok("登录成功");
		}
		return Result.fail("登录失败");
	}

	/**
	 * 查询登录状态
	 * http://localhost:8010/saIsLogin
	 * @return
	 */
	@RequestMapping("saIsLogin")
	@ResponseBody
	public String saIsLogin() {
		boolean isLogin = StpUtil.isLogin();
		if(isLogin) {
			Object loginId = StpUtil.getLoginId();
			return Result.ok("当前会话已登录：" + isLogin + "，loginId：" + loginId);
		}
		return Result.fail("当前会话未登录，isLogin：" + isLogin);
	}

	/**
	 * 测试sa-token注销登录
	 * http://localhost:8010/saDoLogout?username=zhang
	 * @param username
	 * @return
	 */
	@RequestMapping("saDoLogout")
	@ResponseBody
	public String saDoLogout(String username) {
		// 此处仅作模拟示例，真实项目需要从数据库中查询数据进行比对
		if("zhang".equals(username)) {
			StpUtil.logout(10001);
			return Result.ok("注销成功");
		}
		return Result.fail("注销失败");
	}

	/**
	 * 测试sa-token是否拥有角色
	 * http://localhost:8010/saHasRole?username=zhang
	 * @param username
	 * @return
	 */
	@RequestMapping("saHasRole")
	@ResponseBody
	public String saHasRole(String username) {
		boolean hasRole = StpUtil.hasRole("super-admin");
		if(hasRole) {
			return Result.ok("有权限");
		}
		return Result.fail("无权限");
	}

	/**
	 * 测试sa-token是否拥有权限
	 * http://localhost:8010/saHasPermission?username=zhang
	 * @param username
	 * @return
	 */
	@RequestMapping("saHasPermission")
	@ResponseBody
	public String saHasPermission(String username) {
		boolean hasUpdatePermission = StpUtil.hasPermission("user-update");
		if(hasUpdatePermission) {
			return Result.ok("有update权限");
		} else {
			return Result.fail("没有update权限");
		}
	}

	/**
	 * 测试sa-token是否拥有权限
	 * http://localhost:8010/saCheckPermission?username=zhang
	 * @param username
	 * @return
	 */
	@SaCheckPermission("user-add")
	@RequestMapping("saCheckPermission")
	@ResponseBody
	public String saCheckPermission(String username) {
		return Result.ok("有add权限");
	}

	/**
	 * 测试JRebel
	 * http://localhost:8010/testJRebel
	 */
	@RequestMapping("/testJRebel")
	@ResponseBody
	public String testJRebel(HttpServletRequest request){
		logger.info("enter method testJRebel().");
		return "1";
	}

	/**
	 * 测试parallelStream
	 * @return
	 */
	@RequestMapping("/parallelStream")
	@ResponseBody
	public String parallelStream() {
		logger.info("enter method parallelStream().");
		try {
			LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
			IPage<User> page = new Page<>(1, 1000);
			IPage<User> pageList = userService.page(page, queryWrapper);
			pageList.getRecords().parallelStream().forEach(e -> {
				System.out.println(Thread.currentThread().getName()+">>"+e.toString());
			});
			return Result.ok(pageList);
		} catch (Exception e) {
			logger.error("pageList发生异常：{}", e);
			return Result.fail("发生异常");
		}
	}

	/**
	 * 测试邮箱验证码
	 * @return
	 */
	@RequestMapping("/testEmail")
	@ResponseBody
	public String testEmail() {
		logger.info("enter method testEmail().");
		try {
			String to = "1311216585@qq.com";
			String veriCode = SmsVeriCodeUtil.generateSmsVeriCode();
			mailService.send(to, "验证码", veriCode);
			return Result.ok(veriCode);
		} catch (Exception e) {
			logger.error("testEmail发生异常：{}", e);
			return Result.fail("发生异常");
		}
	}

	/**
	 * ③ 测试Neo4j
	 * @return
	 */
	@RequestMapping("/testNeo4j")
	@ResponseBody
	public String testNeo4j() {
		logger.info("enter method testNeo4j().");
		try {
			// 保存student
			Student student = new Student();
			student.setName("student3");
			student.setAge(30);
			student.setHeigh(177);
			student.setWeight(70);
			Student sr = studentRepository.save(student);

			// 保存teacher
			Teacher teacher = new Teacher();
			teacher.setName("teacher3");
			teacher.setAge(30);
			teacher.setSubject("Chinese");
			Teacher tr = teacherRepository.save(teacher);

			// 保存relationship
			RelationShip relationShip = new RelationShip();
			relationShip.setStudent(student);
			relationShip.setTeacher(teacher);
			relationShip.setScore(60);
			relationShip.setIndexName("st2");
			RelationShip rr = relationshipRepository.save(relationShip);

			logger.info(rr.toString());
			return Result.ok(rr.getId());
		} catch (Exception e) {
			logger.error("testNeo4j发生异常：{}", e);
			return Result.fail("发生异常");
		}
	}

	/**
	 * ③ 测试Neo4j
	 * @return
	 */
	@RequestMapping("/testNeo4j2")
	@ResponseBody
	public String testNeo4j2() {
		logger.info("enter method testNeo4j2().");
		try {
			// 查询
			Iterable<Student> list = studentRepository.findAll();
			return Result.ok(list);
		} catch (Exception e) {
			logger.error("testNeo4j发生异常：{}", e);
			return Result.fail("发生异常");
		}
	}

	/**
	 * 测试Disruptor
	 * @return
	 */
	@RequestMapping("/testDisruptor")
	@ResponseBody
	public String testDisruptor() {
		logger.info("enter method testDisruptor().");
		try {
			disruptorMqService.sayHelloMq("hello world!" + JodaUtil.currentTime());
			System.out.println("消息队列已发送完毕");
			//这里停止2000ms是为了确定是处理消息是异步的
			Thread.sleep(2000);
			System.out.println("sleep 2000 ms over");
			return Result.ok();
		} catch (Exception e) {
			logger.error("testDisruptor发生异常：{}", e);
			return Result.fail("发生异常");
		}
	}
	/**
	 * 测试Retryable
	 * @return
	 */
	@RequestMapping("/testRetryable")
	@ResponseBody
	public String testRetryable(int code) {
		logger.info("enter method testRetryable().");
		try {
			testRetryService.test(code);
			return Result.ok();
		} catch (Exception e) {
			logger.error("testDisruptor发生异常：{}", e);
			return Result.fail("发生异常");
		}
	}

}
